package cn.mjc.game.sudoku;

import java.util.Random;
import java.util.logging.Level;

import common.Sudokus;

/**
 * structure of the whole palace.
 * 
 * @author EricMa
 * 
 */
public class NPalace {
	// base size is 3 means number from 1 to 9
	private static final int defaultSize = 3;
	private int size;
	private int palaceSize;
	private int[][] data = null;
	private int[][] answer = null; // for initialize the data

	private NPalace(int size) {
		if (size <= 1)
			size = defaultSize;
		this.size = size;
		this.palaceSize = size * size;
		this.data = new int[palaceSize][palaceSize];
	}

	/**
	 * create a instance of palace with default default size.
	 * 
	 * @return
	 */
	public static NPalace newInstance() {
		return newInstance(defaultSize);
	}

	/**
	 * create a instance with specified size which must greater than 1.
	 * 
	 * @param size
	 * @return
	 */
	public static NPalace newInstance(int size) {
		return new NPalace(size);
	}

	/**
	 * get the size of palace,the return value is that you set to this instance
	 * with {@link #newInstance(int)} or default.
	 * 
	 * @return
	 */
	public int getNSize() {
		return this.size;
	}

	/**
	 * get the size of cell in the whole palace.
	 * 
	 * @return
	 */
	public int getCellSize() {
		return this.palaceSize;
	}

	/**
	 * get the user data value of the whole NPalace and return it in a new
	 * array.
	 * 
	 * @return
	 */
	public int[][] getUserValues() {
		return getValues(this.data);
	}

	/**
	 * get the answer value of the whole NPalace and return it in a new array.
	 * <P/>
	 * <B>call this method before {@link #initialize(Level)} method will return
	 * null.</B>
	 * 
	 * @return
	 */
	public int[][] getAnswerValues() {
		return getValues(this.answer);
	}

	private int[][] getValues(int[][] data) {
		if (data == null)
			return null;
		int[][] result = new int[this.palaceSize][this.palaceSize];
		for (int i = 0; i < this.palaceSize; i++) {
			for (int j = 0; j < this.palaceSize; j++) {
				result[i][j] = data[i][j];
			}
		}
		return result;
	}

	/**
	 * set the value to the specified position of each palace.
	 * 
	 * @param palaceIndex
	 *            index of palace begin with 0
	 * @param x
	 *            x position begin with 0 of specified palace.
	 * @param y
	 *            y position begin with 0 of specified palace.
	 * @param value
	 *            the value between 1 and square of size
	 * @return true means success and false means failed
	 */
	public boolean setValue(int palaceIndex, int x, int y, int value) {
		if (!isValid(palaceIndex, x, y, value))
			return false;
		data[palaceIndex % size * size + x][palaceIndex / size * size + y] = value;
		return true;
	}

	/**
	 * set the value to the specified position of the whole palace.
	 * 
	 * @param x
	 *            x coordination begin with 0 of the whole palace.
	 * @param y
	 *            y coordination begin with 0 of the whole palace.
	 * @param value
	 *            the value between 1 and square of size
	 * @return true means success and false means failed
	 */
	public boolean setValue(int x, int y, int value) {
		if (!isValid(x, y, value))
			return false;
		data[x][y] = value;
		return true;
	}

	/**
	 * get the value of specified position of given palace.
	 * 
	 * @param palaceIndex
	 *            index of palace begin with 0 in the whole palace
	 * @param x
	 *            x position begin with 0 of specified palace
	 * @param y
	 *            y position begin with 0 of specified palace.
	 * @return 0 means initialized state,-1 means error,the other positive value
	 *         means success.
	 */
	public int getValue(int palaceIndex, int x, int y) {
		if (palaceIndex < 0 || palaceIndex >= palaceSize)
			return -1; // out of bound
		if (x < 0 || x >= size)
			return -1; // out of x bound
		if (y < 0 || y >= size)
			return -1; // out of y bound
		return data[palaceIndex * size + x][palaceIndex / size * size + y];
	}

	/**
	 * get the value of specified position of the whole palace.
	 * 
	 * @param x
	 *            x coordination begin with 0 of the whole palace.
	 * @param y
	 *            y coordination begin with 0 of the whole palace.
	 * @return 0 means initialized state,-1 means error,the other positive value
	 *         means success.
	 */
	public int getValue(int x, int y) {
		if (x < 0 || x >= palaceSize)
			return -1; // out of x bound
		if (y < 0 || y >= palaceSize)
			return -1; // out of x bound
		return data[x][y];
	}

	/**
	 * get the values of small palace which contain the position with given
	 * coordinates in parameter.
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public int[][] getPalaceValues(int x, int y) {
		int tsize = (x + 1) * (y + 1) + x;
		int palaceIndex = tsize / palaceSize;
		if (tsize % palaceSize > 0)
			palaceIndex += 1;
		return getPalaceValues(palaceIndex);
	}

	/**
	 * get the values of specified small palace with given palace index.
	 * 
	 * @param palaceIndex
	 * @return
	 */
	public int[][] getPalaceValues(int palaceIndex) {
		if (palaceIndex < 0 || palaceIndex >= palaceSize)
			return null; // out of bound
		int startX = palaceIndex / size * size, startY = palaceIndex / size * size;
		int[][] result = new int[size][size];
		for (int x = startX, rx = 0; x < startX + size; x++, rx++) {
			for (int y = startY, ry = 0; y < startY + size; y++, ry++) {
				result[rx][ry] = data[x][y];
			}
		}
		return result;
	}

	/**
	 * check the value in the specified position is valid or not.
	 * 
	 * @param palaceIndex
	 *            index of palace begin with 0
	 * @param x
	 *            x position begin with 0 of specified palace.
	 * @param y
	 *            y position begin with 0 of specified palace.
	 * @param value
	 *            the value between 1 and square of size
	 * @return true means valid and false means invalid
	 */
	public boolean isValid(int palaceIndex, int x, int y, int value) {
		if (palaceIndex < 0 || palaceIndex >= palaceSize)
			return false; // out of bound
		if (x < 0 || x >= size)
			return false; // out of x bound
		if (y < 0 || y >= size)
			return false; // out of y bound
		if (value < 1 || value > palaceSize)
			return false; // out of value bound
		int startX = palaceIndex / size * size, startY = palaceIndex / size * size;
		// check current small palace
		for (int _x = startX; _x < startX + size; _x++) {
			for (int _y = startY; _y < startY + size; _y++) {
				if (data[_x][_y] == value)
					return false; // already exists this value
			}
		}
		// check the whole palace in current row and column
		for (int _x = startX + x, _y = 0; _y < palaceSize; _y++) {
			// check column
			if (data[_x][_y] == value)
				return false; // already exists this value
		}
		for (int _x = 0, _y = startY + y; _x < palaceSize; _x++) { // check row
			if (data[_x][_y] == value)
				return false; // already exists this value
		}
		return true;
	}

	/**
	 * check the value in specified position is valid or not.
	 * 
	 * @param x
	 *            x coordination begin with 0 of the whole palace.
	 * @param y
	 *            y coordination begin with 0 of the whole palace.
	 * @param value
	 *            the value between 1 and square of size
	 * @return true means valid and false means invalid
	 */
	public boolean isValid(int x, int y, int value) {
		int tsize = (x + 1) * (y + 1) + x;
		int palaceIndex = tsize / palaceSize;
		if (tsize % palaceSize > 0)
			palaceIndex += 1;
		int sx = x % size, sy = y % size;
		return isValid(palaceIndex, sx, sy, value);
	}

	/**
	 * check the every cell in palace was put valid value.
	 * 
	 * @return
	 */
	public boolean isDone() {
		for (int x = 0; x < palaceSize; x++) {
			for (int y = 0; y < palaceSize; y++) {
				if (data[x][y] == Sudokus.EMPTY_NUMBER)
					return false;
			}
		}
		return true;
	}

	/**
	 * print the values that set by user.
	 */
	public void printUserValue() {
		System.out.println(getString(this.data));
	}

	/**
	 * print the answer values.
	 */
	public void printAnswerValue() {
		if (this.answer == null) {
			System.out.println("No solution!");
			return;
		}
		System.out.println(getString(this.answer));
	}

	/**
	 * random initialize and fill the user data and resolve the answer.If there
	 * are no solution will return false.
	 */
	public boolean fill(NResolver resolver) {
		// first initialize the first row randomly.
		Random vr = new Random();
		for (int x = 0; x < this.palaceSize; x++) {
			int value = Sudokus.EMPTY_NUMBER;
			while (value == Sudokus.EMPTY_NUMBER) {
				value = vr.nextInt(this.palaceSize) + 1;
				for (int _x = 0; _x < x; _x++) {
					if (data[_x][0] == value) {
						value = Sudokus.EMPTY_NUMBER;
						break;
					}
				}
			}
			data[x][0] = value;
		}
		// resolve the answer
		this.answer = resolver.resolve(this);
		if (this.answer == null)
			return false;
		// fill the user data array
		for (int x = 0; x < palaceSize; x++) {
			for (int y = 0; y < palaceSize; y++) {
				data[x][y] = answer[x][y];
			}
		}
		return true;
	}

	/**
	 * shield some number from the user data array,the parameter minBlank means
	 * the minimize blank numbers in the each row and the parameter maxBlank
	 * means the maximize blank numbers in the each row.
	 * <P/>
	 * The parameter minBlank and maxBlank must between 1 and square of value
	 * that you set to the constructor(include bounds),and the maxBlank should
	 * greater than minBlank.
	 * 
	 * @param minBlank
	 *            minimize blank number in each row
	 * @param maxBlank
	 *            maximize blank number in each row
	 */
	public boolean shield(int minBlank, int maxBlank) {
		if (minBlank > maxBlank)
			return false;
		if (minBlank < 1 || minBlank > palaceSize)
			return false;
		if (maxBlank < 1 || maxBlank > palaceSize)
			return false;
		Random random = new Random();
		for (int y = 0; y < palaceSize; y++) {
			int blanks = random.nextInt(maxBlank - minBlank + 1) + minBlank;
			int times = 0;
			while (times != blanks) {
				int x = random.nextInt(palaceSize);
				if (data[x][y] != Sudokus.EMPTY_NUMBER) {
					data[x][y] = Sudokus.EMPTY_NUMBER;
					++times;
				}
			}
		}
		return true;
	}

	/**
	 * print the current value of user data.
	 */
	@Override
	public String toString() {
		return getString(data);
	}

	/**
	 * get the string of whole palace.
	 */
	private String getString(int[][] data) {
		StringBuilder buf = new StringBuilder();
		int width = String.valueOf(palaceSize).length();
		for (int y = 0; y < palaceSize; y++) {
			for (int x = 0; x < palaceSize; x++) {
				buf.append(String.format("%" + width + "d", data[x][y]));
				if (x < palaceSize - 1)
					buf.append("   ");
			}
			buf.append("\r\n");
		}
		return buf.toString();
	}

	public static void main(String[] args) {
		NPalace palace = NPalace.newInstance(4);
		/*
		 * palace.setValue(3, 2, 6); palace.setValue(6, 8, 9);
		 * palace.setValue(2, 2, 1, 5); palace.setValue(2, 1, 2, 3);
		 * palace.printUserValue();
		 */
		palace.fill(new ExhaustiveNResolver(palace));
		palace.shield(3, 7);
		palace.printUserValue();
		System.out.println("===================");
		palace.printAnswerValue();
	}
}
